package org.dromara.common.excel.core;

import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.common.core.exception.ServiceException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：<h1>Excel下拉可选项</h1>
 * 注意：为确保下拉框解析正确，传值务必使用createOptionValue()做为值的拼接
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("unused")
public class DropDownOptions {
    /**
     * 一级下拉所在列index，从0开始算
     */
    private int index = 0;

    /**
     * 二级下拉所在的index，从0开始算，不能与一级相同
     */
    private int nextIndex = 0;

    /**
     * 一级下拉所包含的数据
     */
    private List<String> options = new ArrayList<>();

    /**
     * 二级下拉所包含的数据Map
     * <p>以每一个一级选项值为Key，每个一级选项对应的二级数据为Value</p>
     */
    private Map<String, List<String>> nextOptions = new HashMap<>();

    /**
     * 分隔符
     */
    private static final String DELIMITER = "_";

    /**
     * 创建只有一级的下拉选
     *
     * @param index    下拉选的索引值，用于在表单中唯一标识该下拉选
     * @param options  下拉选的选项列表，包含所有可选项的文本
     */
    public DropDownOptions(int index, List<String> options) {
        this.index = index;
        this.options = options;
    }

    /**
     * <h2>创建每个选项可选值</h2>
     * <p>注意：不能以数字，特殊符号开头，选项中不可以包含任何运算符号</p>
     * 该方法接收一个可变参数，每个参数代表一个选项值片段，将它们连接成一个合法的选项值字符串
     * 选项值片段必须符合规则：仅允许使用中英文字符以及数字
     * 片段之间使用预定义的分隔符进行分隔
     *
     * @param vars 可选值内包含的参数，代表不同的选项值片段
     * @return 合规的可选值
     * @throws ServiceException 如果选项值片段不符合规则或以数字开头，则抛出服务异常
     */
    public static String createOptionValue(Object... vars) {
        StringBuilder stringBuffer = new StringBuilder();
        String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
        for (int i = 0; i < vars.length; i++) {
            // 将对象转换为字符串，并去除首尾空白字符
            String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
            if (!var.matches(regex)) {
                throw new ServiceException("选项数据不符合规则，仅允许使用中英文字符以及数字");
            }
            stringBuffer.append(var);

            // 直至最后一个片段前，都以分隔符进行分隔
            if (i < vars.length - 1) {
                // 直至最后一个前，都以_作为切割线
                stringBuffer.append(DELIMITER);
            }
        }

        // 禁止以数字开头的选项值
        if (stringBuffer.toString().matches("^\\d_*$")) {
            throw new ServiceException("禁止以数字开头");
        }
        return stringBuffer.toString();
    }

    /**
     * 将处理后合理的可选值解析为原始的参数
     *
     * @param option 经过处理后的合理的可选项
     * @return 原始的参数
     */
    public static List<String> analyzeOptionValue(String option) {
        // 使用自定义的字符串工具类StrUtil进行分割操作，DELIMITER为预定义的分隔符常量
        // 第一个true表示分割后的字符串列表需要去除每个元素两端的空白字符
        // 第二个true表示如果遇到连续的多个分隔符，则只将它们视为一个分隔符进行分割
        return StrUtil.split(option, DELIMITER, true, true);
    }

    /**
     * 创建级联下拉选项
     *
     * @param parentList                  父实体可选项原始数据
     * @param parentIndex                 父下拉选位置
     * @param sonList                     子实体可选项原始数据
     * @param sonIndex                    子下拉选位置
     * @param parentHowToGetIdFunction    父类如何获取唯一标识
     * @param sonHowToGetParentIdFunction 子类如何获取父类的唯一标识
     * @param howToBuildEveryOption       如何生成下拉选内容
     * @return 级联下拉选项
     */
    public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
                                                         int parentIndex,
                                                         List<T> sonList,
                                                         int sonIndex,
                                                         Function<T, Number> parentHowToGetIdFunction,
                                                         Function<T, Number> sonHowToGetParentIdFunction,
                                                         Function<T, String> howToBuildEveryOption) {
        DropDownOptions parentLinkSonOptions = new DropDownOptions();
        // 先创建父类的下拉
        // 设置父级在级联下拉菜单中的索引
        parentLinkSonOptions.setIndex(parentIndex);
        // 根据父级列表构建父级下拉选项
        parentLinkSonOptions.setOptions(
            parentList.stream()
                .map(howToBuildEveryOption)
                .collect(Collectors.toList())
        );
        // 提取父-子级联下拉
        // 初始化子级选项的Map，用于存储每个父级对应的子级选项
        Map<String, List<String>> sonOptions = new HashMap<>();
        // 父级依据自己的ID分组
        // 根据父级ID对父级列表进行分组
        Map<Number, List<T>> parentGroupByIdMap =
            parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
        // 遍历每个子集，提取到Map中
        // 遍历子级列表，提取每个子级的选项并存储到Map中
        sonList.forEach(everySon -> {
            // 检查当前子级的父ID是否存在于父级分组Map中
            if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
                // 找到对应的上级
                // 获取当前子级的父级对象
                T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
                // 提取名称和ID作为Key
                // 构建父级选项的Key
                String key = howToBuildEveryOption.apply(parentObj);
                // Key对应的Value
                // 获取Key对应的子级选项List，如果不存在则新建一个
                List<String> thisParentSonOptionList;
                if (sonOptions.containsKey(key)) {
                    thisParentSonOptionList = sonOptions.get(key);
                } else {
                    thisParentSonOptionList = new ArrayList<>();
                    sonOptions.put(key, thisParentSonOptionList);
                }
                // 往Value中添加当前子集选项
                // 将当前子级选项添加到对应的父级选项的List中
                thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
            }
        });
        // 设置二级下拉所在的index，从0开始算，不能与一级相同
        parentLinkSonOptions.setNextIndex(sonIndex);
        // 设置二级下拉所包含的数据Map
        // * <p>以每一个一级选项值为Key，每个一级选项对应的二级数据为Value</p>
        parentLinkSonOptions.setNextOptions(sonOptions);
        return parentLinkSonOptions;
    }
}
